package software.lib.server;

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.logging.Logger;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import java.util.zip.ZipEntry;

/**
 * Created by Wang on 2017/5/9.
 */
public class BeanScanner {

    protected static Logger logger = Logger.getLogger(BeanScanner.class.getName());

    public static void main(String[] args) throws URISyntaxException {
//        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//        System.out.println(classLoader.getResource("/").getFile());
        scannerJar("org").parallelStream().map(Class::getName)
                .sorted()
                .sequential()
                .forEach(System.out::println);
    }

    public static List<Class> scannerJar(String... packages){
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Map<String,List<URL>> urlmap =
            Arrays.stream(packages)
                .map(p->p.replace(".","/")).parallel()
                .<Enumeration<URL>>map(p->{
                    try {
                        return classLoader.getResources(p);
                    } catch (IOException e) {
                        e.printStackTrace();
                        return Collections.emptyEnumeration();
                    }
                }).<ArrayList<URL>>map(Collections::list)
                .flatMap(e->e.stream())
                .collect(Collectors.groupingBy(URL::getProtocol));

        List<URL> jarList = urlmap.get("jar");

        Stream<Class> classList = null;

        if(jarList!=null){
            classList = jarList.parallelStream().<Enumeration<JarEntry>>map(url -> {
                try {
                    return ((JarURLConnection) url.openConnection()).getJarFile().entries();
                } catch (IOException e) {
                    e.printStackTrace();
                    return Collections.emptyEnumeration();
                }
            }).<ArrayList<JarEntry>>map(Collections::list).<JarEntry>flatMap(List::stream).parallel()
                    .map(ZipEntry::getName)
                    .filter(f->f.endsWith(".class"))
                    .map(p->StringUtils.removeEndIgnoreCase(p,".class"))
                    .map(p->p.replace("/","."))
                    .map(p->StringUtils.stripStart(p,"."))
                    .filter(p->StringUtils.startsWithAny(p,packages))
                    .<Optional<Class>>map(p->{
                        try {
                            return Optional.of(classLoader.loadClass(p));
                        } catch (NoClassDefFoundError|ClassNotFoundException e) {
                            logger.info("Cannot load the class:"+p);
//                            System.err.println("Cannot load the class:"+p);
//                            e.printStackTrace();
                            return Optional.empty();
                        }
                    }).filter(p->p.isPresent()).map(Optional::get);
//                    .collect(Collectors.toList());
        }

        List<URL> fileList = urlmap.get("file");
        List<File> rawStream = Collections.emptyList();
        if(fileList != null)
            rawStream = fileList.parallelStream()
                .map(url -> {
                    try {
                        return url.toURI();
                    } catch (URISyntaxException e) {
                        return null;
                    }
                }).filter(x->x!=null)
                .map(Paths::get)
                .map(Path::toFile)
                .collect(Collectors.toList());

        String basepath = classLoader.getResource(".").getFile();
        if(classList == null){
            classList = getClassStream(basepath,rawStream);;
        }else {
            classList = Stream.concat(classList,getClassStream(basepath,rawStream));
        }

        return classList.collect(Collectors.toList());
    }

    public static Stream<Class> scannerFile(String... packages){
        String path = BeanScanner.class.getResource("/").getPath();
        List<File> rawStream = Arrays.stream(packages)
                .map(p->"/"+p.replace(".","/"))
                .map(BeanScanner.class::getResource)
                .map(URL::getPath)
                .map(Paths::get)
                .map(Path::toFile)
                .collect(Collectors.toList());

        Stream<Class> classList = getClassStream(path,rawStream);
        return classList;
    }

    private static Stream<Class> getClassStream(String basepath,List<File> rawStream) {
        String path = StringUtils.strip(basepath.replace('/','.').replace('\\','.'),".");
        Stream<File> fileStream = Stream.empty();

        while (!rawStream.isEmpty()){
            Map<Boolean,List<File>> map =
                    rawStream.parallelStream()
//                            .peek(f->System.out.println(f.getName()))
                            .collect(Collectors.groupingBy(File::isFile));
            if(map.get(true)!=null)
                fileStream = Stream.concat(fileStream,map.get(true).stream());
            //rawStream = map.get(false).parallelStream().map(File::listFiles).<List<File>>map(Arrays::asList).<Stream<File>>flatMap(l->l.stream());
            if(map.get(false)!=null)
                rawStream = map.get(false).parallelStream().<File>flatMap(f-> Arrays.<File>stream(f.listFiles())).collect(Collectors.toList());
            else rawStream = Collections.emptyList();
        }

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//        List<String> classpathList =
//          List<Class> classList =
        //                .forEach(println);
//                        .collect(Collectors.toList());

        Consumer<String> println = System.out::println;
        return fileStream.filter(file -> file.getName().endsWith(".class"))
                .map(File::getPath)
//                .peek(println)
                .map(p->p.replace("\\","."))
                .map(p->p.replace("/","."))
//                .peek(println)
                .map(p->p.replace(path,""))
//                .peek(println)
                .map(p-> StringUtils.removeEndIgnoreCase(p,".class"))
//                .peek(println)
                .map(p->StringUtils.stripStart(p,"\\."))
                .<Class>map(p->{
                    try {
                        return classLoader.loadClass(p);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                        return null;
                    }
                }).filter(p->p!=null);
    }
}
